package com.sinog.front.config.idempotent;

import com.sinog.core.utilbean.Result;
import com.sinog.core.utilbean.ReturnCode;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @desc 接口幂等性切面实现
 * @author lzq
 * @date 2021-01-21 16:41
 */
@Aspect
@Order(1)
@Component
public class IdempotentAspect {

    private static final String REQUEST_KEY = "RequestKey:";
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 定义切点
     */
    @Pointcut("execution(* com.sinog.front.controller..*(..))")
    public void serviceLog1() {
    }

    /**
     * 描述
     * @param joinPoint joinPoint
     * @return Object
     */
    @Around("serviceLog1()")
    public Object apiIdempotentCheck(ProceedingJoinPoint joinPoint) throws Throwable {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        assert null != requestAttributes;
        HttpServletRequest request = (HttpServletRequest)requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        //宿主机访问地址
        String ipAddr = "";
        int length = joinPoint.getArgs().length;
        for(int i = 0; i < length; i++) {
            Object ll = joinPoint.getArgs()[i];
            if(ll instanceof HttpServletRequest) {
                request = (HttpServletRequest)ll;
                ipAddr = request.getHeader("X-Forwarded-For");
                // 取不到forwarded地址就取实际的ip地址
                if(null == ipAddr || ipAddr.isEmpty()) {
                    ipAddr = request.getRemoteAddr();
                } else {
                    // 如果有多级反向代理，返回的是一组ip，取第一个
                    ipAddr = ipAddr.split(",")[0];
                }
                break;
            }
        }
        assert null != request;
        String requestUrl = request.getRequestURI();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        Method method = methodSignature.getMethod();
        Idempotent annotation = method.getAnnotation(Idempotent.class);
        //无注解直接放行
        if(null == annotation) {
            return joinPoint.proceed();
        }
        //获取请求参数
        int[] i = annotation.paramPos();
        StringBuilder stringBuilder = new StringBuilder();
        for(int k : i) {
            Object arg = joinPoint.getArgs()[k];
            stringBuilder.append(arg).append("::");
        }
        String apiName = null;
        String apiMethodName;
        String key;
        //接口方法名称
        apiMethodName = method.getName();
        //获取接口间隔时间
        long l = annotation.expireTime();
        Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
        apiName = getRequestName(requestUrl,apiName,declaredAnnotations);
        //如果用户自定义则默认使用修改后的规则
        if(StringUtils.isNotBlank(annotation.key())) {
            key = annotation.key();
        } else {
            key = REQUEST_KEY + ":" + apiName + ":" + apiMethodName + ":" + ipAddr + ":" + stringBuilder;
        }
        //如果当前key存在直接抛出异常
        Object proceed;
        synchronized (this){
            if(Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
                return Result.failure(ReturnCode.NO_COMMIT);
            }
            if(ObjectUtils.isNotEmpty(stringBuilder)) {
                stringRedisTemplate.opsForValue().set(key,((Object)stringBuilder).toString(),l,TimeUnit.SECONDS);
            } else {
                stringRedisTemplate.opsForValue().set(key,"1",l,TimeUnit.SECONDS);
            }
            proceed = joinPoint.proceed();
            //默认不删除 如果修改则使用修改后的配置
            if(annotation.delKey()) {
                stringRedisTemplate.delete(key);
            }
        }
        return proceed;
    }

    /**
     * 获取接口请求类型以及接口api
     * @param requestUrl requestUrl
     * @param apiName apiName
     * @param declaredAnnotations declaredAnnotations
     * @return String
     */
    private String getRequestName(String requestUrl,String apiName,Annotation[] declaredAnnotations) {
        for(Annotation declaredAnnotation : declaredAnnotations) {
            if(declaredAnnotation instanceof RequestMapping) {
                RequestMapping requestMapping = (RequestMapping)declaredAnnotation;
                String requestType = requestMapping.method().length <= 0 ? "DEFAULT" : requestMapping.method()[0].name();
                apiName = requestType + ":" + requestUrl;
            }
            if(declaredAnnotation instanceof DeleteMapping) {
                apiName = "DELETE:" + requestUrl;
            }
            if(declaredAnnotation instanceof GetMapping) {
                apiName = "GET:" + requestUrl;
            }
            if(declaredAnnotation instanceof PutMapping) {
                apiName = "PUT:" + requestUrl;
            }
            if(declaredAnnotation instanceof PostMapping) {
                apiName = "POST:" + requestUrl;
            }
        }
        return apiName;
    }
}